从零开始教你写一个LLVM Pass 您所在的位置:网站首页 pass if 从零开始教你写一个LLVM Pass

从零开始教你写一个LLVM Pass

2024-07-08 10:39| 来源: 网络整理| 查看: 265

失业在家闲来无事,不如写些文章回馈下社区。

如果你已经有一定基础,这里是建议您直接看 LLVM的加Pass的 官方文档。可能限制于自身经验以及专有名词语言的障碍,您可以看下我这篇文章,希望对您有所帮助,如果有不清楚的欢迎留言或查看官方文档。

本文以LLVM Release 15.0.7: Jan 2023 中的 AMDGPUResourceUsageAnalysisPass 为例,对应的commit id是8dfdcc7, 和你一起剖析他是怎么加进去的

一、什么是Pass

LLVM Pass是针对LLVM编译器基础设施的插件式组件,用于执行编译器优化或代码分析。

1、所有的LLVM Pass都是Pass的子类

我们 AMDGPUResourceUsageAnalysis 是这样定义的,llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.h:27,其继承于 ModulePass

struct AMDGPUResourceUsageAnalysis : public ModulePass { static char ID;

我们可以在 llvm/include/llvm/Pass.h:248 中看到 ModulePass 是基于 Pass 的子类

//===----------------------------------------------------------------------===// /// ModulePass class - This class is used to implement unstructured /// interprocedural optimizations and analyses. ModulePasses may do anything /// they want to the program. /// class ModulePass : public Pass { public:

AMDGPUResourceUsageAnalysis 这个Pass是为了完成栈、寄存器等相关信息的计算,待到指令打印的时候用的。

ModulePass 是用于在整个编译单元(Module)级别执行优化和分析操作,作用于编译单元的中间表示(IR),而不是单个函数或基本块。

2、Pass的目的各种各样

在LLVM中,编译过程可以分为多个阶段,每个阶段都可以通过插件式的方式添加和修改。其可以用于多种目的,包括但不限于:

优化: 这些Pass用于改进生成的机器码的性能和效率,例如执行常量传播、循环优化、内联等。

分析: 这些Pass用于对代码进行静态分析,例如查找未使用的变量、检测内存泄漏、执行指令计数等。

代码转换: 这些Pass用于在中间表示的不同形式之间进行转换,例如将高级语言代码转换为中间表示,或者执行特定的代码重组。

除上文提到的 Pass子类 ModulePass 外,还有 CallGraphSCCPass 在用于在调用图(call graph)上从下至上的遍历程序,SCC是strongly connected(强连通分量,转换DAG用),LoopPass在每个每个循环上执行,RegionPass在函数中的每个单入口单出口region执行,知乎文章参考 官方文档还有 LLVM’s Analysis and Transform Passes 清单

3、绝大多数Pass的输入是IR

IR中间表示是什么呢,就是经过LLVM前端处理后生成的一种抽象的、与具体硬件无关的表示,且可以展示为ISD,经过后端处理后Node可能会降级为目标平台特有的ISD。IR分类和解释详见 官方文档,或者看看这个大佬总结的也挺好。IR中涉及的Module、Function、Basic Block(BB) 如下图所示,我们后端往往拿到的就是其中的一个结构,他们是一个嵌套关系。

二、基础代码 1、build环境的问题

官方文档使用的是opt -load 库文件来跑的,由于我们是改在LLVM后端的,所以仅需在你需要修改的Target增加需要编译的文件即可,比如 AMDGPUResourceUsageAnalysis.cpp 可以在 llvm/lib/Target/AMDGPU/CMakeLists.txt:93 看到

2、写Pass需要一些头文件来引入库

你需要引入 llvm/Pass.h 文件,AMDGPUResourceUsageAnalysis 引入了 CallGraphSCCPass.h , CallGraphSCCPass.h 内 引入了 llvm/Pass.h llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.h:18

#ifndef LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H #define LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H #include "llvm/Analysis/CallGraphSCCPass.h" #include "llvm/CodeGen/MachineModuleInfo.h" namespace llvm { 3、定义这个Pass

选好你需要继承的Pass类,我们这个Pass要知道全局的信息,所以使用的ModulePass。

这个Pass需要一个ID,并而且是static的,即只有一份 构造函数也是需要的,AMDGPUResourceUsageAnalysis() : ModulePass(ID) {}

还有runOnModule函数需要重写,bool runOnModule(Module &M) override; ,这个往往具体实现在 .cpp 中

这里还重写了 doInitialization 去 做初始化

其他就是你需要定义的成员以及你抽象的接口了

//===- AMDGPUResourceUsageAnalysis.h ---- analysis of resources -*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // /// \file /// \brief Analyzes how many registers and other resources are used by /// functions. /// //===----------------------------------------------------------------------===// #ifndef LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H #define LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H #include "llvm/Analysis/CallGraphSCCPass.h" #include "llvm/CodeGen/MachineModuleInfo.h" namespace llvm { class GCNSubtarget; class MachineFunction; class TargetMachine; struct AMDGPUResourceUsageAnalysis : public ModulePass { static char ID; public: // Track resource usage for callee functions. struct SIFunctionResourceInfo { // Track the number of explicitly used VGPRs. Special registers reserved at // the end are tracked separately. int32_t NumVGPR = 0; int32_t NumAGPR = 0; int32_t NumExplicitSGPR = 0; uint64_t PrivateSegmentSize = 0; bool UsesVCC = false; bool UsesFlatScratch = false; bool HasDynamicallySizedStack = false; bool HasRecursion = false; bool HasIndirectCall = false; int32_t getTotalNumSGPRs(const GCNSubtarget &ST) const; // Total number of VGPRs is actually a combination of AGPR and VGPR // depending on architecture - and some alignment constraints int32_t getTotalNumVGPRs(const GCNSubtarget &ST, int32_t NumAGPR, int32_t NumVGPR) const; int32_t getTotalNumVGPRs(const GCNSubtarget &ST) const; }; AMDGPUResourceUsageAnalysis() : ModulePass(ID) {} bool doInitialization(Module &M) override { CallGraphResourceInfo.clear(); return ModulePass::doInitialization(M); } bool runOnModule(Module &M) override; void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired(); AU.setPreservesAll(); } const SIFunctionResourceInfo &getResourceInfo(const Function *F) const { auto Info = CallGraphResourceInfo.find(F); assert(Info != CallGraphResourceInfo.end() && "Failed to find resource info for function"); return Info->getSecond(); } private: SIFunctionResourceInfo analyzeResourceUsage(const MachineFunction &MF, const TargetMachine &TM) const; void propagateIndirectCallRegisterUsage(); DenseMap CallGraphResourceInfo; }; } // namespace llvm #endif // LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H 4、runOnModule 或 runOnFunction 实现

后端的所有要做的事情其实就是一个个的Pass,由 PassManager 进行管理,可能是各种类型的Pass,ModulePass是通过 llvm/lib/IR/LegacyPassManager.cpp:1545 LocalChanged |= MP->runOnModule(M); 来运行的,runOnModule 就是这个 Pass 的入口;FunctionPass 是通过 llvm/lib/IR/LegacyPassManager.cpp:1430 LocalChanged |= FP->runOnFunction(F); 来运行的。

llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.cpp:100 代码中 可以看到了其拿到了这个Module 的 CallGraph,然后进行后序遍历,也就是左右根,然后进行了analyzeResourceUsage,这个函数里我们可以看到其会判断子call然后求大,即 CalleeFrameSize = std::max(I->second.PrivateSegmentSize, CalleeFrameSize); ,当前函数所用栈 = callee中的最大栈 + 当前函数的PrivateSegmentSize,而且你是后序遍历这个图的,也就是把子调用求完再求父的,不需要递归

bool AMDGPUResourceUsageAnalysis::runOnModule(Module &M) { auto *TPC = getAnalysisIfAvailable(); if (!TPC) return false; MachineModuleInfo &MMI = getAnalysis().getMMI(); const TargetMachine &TM = TPC->getTM(); bool HasIndirectCall = false; CallGraph CG = CallGraph(M); auto End = po_end(&CG); for (auto IT = po_begin(&CG); IT != End; ++IT) { Function *F = IT->getFunction(); if (!F || F->isDeclaration()) continue; MachineFunction *MF = MMI.getMachineFunction(*F); assert(MF && "function must have been generated already"); auto CI = CallGraphResourceInfo.insert( std::make_pair(F, SIFunctionResourceInfo())); SIFunctionResourceInfo &Info = CI.first->second; assert(CI.second && "should only be called once per function"); Info = analyzeResourceUsage(*MF, TM); HasIndirectCall |= Info.HasIndirectCall; } if (HasIndirectCall) propagateIndirectCallRegisterUsage(); return false; } 三、注入到后端中 1、初始化Pass

在 llvm/lib/Target/AMDGPU/AMDGPU.h:262 声明

void initializeAMDGPUResourceUsageAnalysisPass(PassRegistry &); extern char &AMDGPUResourceUsageAnalysisID;

在 llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp:401 LLVMInitializeAMDGPUTarget 函数中调用

initializeAMDGPUResourceUsageAnalysisPass(*PR); initializeGCNNSAReassignPass(*PR); initializeGCNPreRAOptimizationsPass(*PR); } 2、Pass 执行和信息使用

这个Pass是在指令打印时使用的,其通过 &getAnalysis() 取到了这个Pass的信息

bool AMDGPUAsmPrinter::runOnMachineFunction(MachineFunction &MF) { // Init target streamer lazily on the first function so that previous passes // can set metadata. if (!IsTargetStreamerInitialized) initTargetStreamer(*MF.getFunction().getParent()); ResourceUsage = &getAnalysis(); CurrentProgramInfo = SIProgramInfo();

需要注意的是其重写了 getAnalysisUsage 确保能拿到数据并执行正确

void AMDGPUAsmPrinter::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); AU.addPreserved(); AsmPrinter::getAnalysisUsage(AU); }

其他情况呢,比如 AMDGPUAlwaysInlinePass 总是强制内联Pass 他是在 void AMDGPUPassConfig::addIRPasses() { 加入的,addIRPasses 表示他的阶段,当然这也是 重写的函数 还可以加在不同的阶段,当然你想知道当前Target用了哪些Pass 查看 TargetPassConfig 和 XXPassConfig 即可

class AMDGPUPassConfig : public TargetPassConfig { public: AMDGPUPassConfig(LLVMTargetMachine &TM, PassManagerBase &PM); AMDGPUTargetMachine &getAMDGPUTargetMachine() const { return getTM(); } ScheduleDAGInstrs * createMachineScheduler(MachineSchedContext *C) const override; void addEarlyCSEOrGVNPass(); void addStraightLineScalarOptimizationPasses(); void addIRPasses() override; void addCodeGenPrepare() override; bool addPreISel() override; bool addInstSelector() override; bool addGCPasses() override;

另外可以根据用户不同的编译选项添加不同的Pass

if (getOptLevel() == CodeGenOpt::Aggressive) addPass(createGVNPass());

llvm/lib/Transforms 文件夹下更是Pass集中地,标量优化在Scalar目录,过程间优化在 IPO 目录。比如 死代码消除 ADCELegacyPass在 llvm/lib/Passes/PassBuilderPipelines.cpp:561 我们可以看到其是默认启用的。

本文来自博客园,作者:暴力都不会的蒟蒻,转载请注明原文链接:https://www.cnblogs.com/BobHuang/p/17640378.html



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有